﻿#include <thread>
#include <chrono>
#include <random>
#include <iostream>
#include <functional>

#include "engine_impl.hh"
#include "command_option.hh"
#include "service.hh"
#include "daemon.hh"
#include "frame_delayer.hh"
#include "path_pool.hh"

#include "thirdparty/kconfig/interface/config_factory.h"

#include "../corelib/logger/writer.hh"
#include "../corelib/logger/appender.hh"
#include "../corelib/logger/file_appender.hh"
#include "../corelib/event/event_queue.hh"
#include "../corelib/time_util.hh"
#include "../corelib/util/os_util.hh"

#ifndef WIN32
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#endif // WIN32

namespace kratos { namespace engine {

EngineImpl* global_engine = nullptr;

#ifndef WIN32
static void signalHandler(int sig) {
    if (sig == SIGQUIT || sig == SIGTERM) {
        if (global_engine) {
            global_engine->stop();
        }
    }
}
#endif // WIN32

EngineImpl::EngineImpl()
    : run_(false), grid_(actor::iref<grid::Grid>()), config_(nullptr), findTimeout_(1000),
        now_(0), moduleManager_(this), http_(nullptr)/*, pathPool_(new PathPool(this))*/, hashType_(HashType::HASH_TYPE_CIRCLE) {
}

EngineImpl::~EngineImpl() {
    global_engine = nullptr;
    actor::deref(grid_);
    if (config_) {
        ConfigFactory::destroyConfig(config_);
    }
    if (http_) {
        khttp::HttpFactory::destroyHttp(http_);
    }
    config_ = nullptr;
    http_ = nullptr;
}

bool EngineImpl::start(const std::string& configFilePath) {
    if (run_) {
        return false;
    }
    if (!CommandOptionsRef.isDebug()) {
        loggerRef.selectAppender(corelib::AppenderType::Console);
    }
    try {
        config_ = ConfigFactory::createConfig();
        if (!config_) {
            logfatal << "ConfigFactory::createConfig() failure" << logend;
            return false;
        }
        if (configFilePath.empty()) {
            config_->load("", CommandOptionsRef.getConfigFilePath());
        } else {
            config_->load("", configFilePath);
        }
        // parse config
        if (!parseConfig()) {
            return false;
        }
    } catch (std::exception& e) {
        logfatal << "Config file error:" << e.what() << corelib::logend;
        return false;
    }
    if (!startCore(false)) {
        return false;
    }
    if (!CommandOptionsRef.isDebug()) {
        global_engine = this;
        main_loop();
    }
    return true;
}

bool EngineImpl::startCore(bool noModule) {
    try {
        // start logger
        if (!CommandOptionsRef.isDebug()) {
            if (!startLogger()) {
                return false;
            }
        }
    } catch (std::exception& e) {
        logfatal << "logger error:" << e.what() << corelib::logend;
        return false;
    }
    try {
        loggerRef.start();
    } catch (std::exception& e) {
        logfatal << "logger error:" << e.what() << corelib::logend;
        return false;
    }
    //try {
    //    if (!pathPool_->start(config_)) {
    //        return false;
    //    }
    //} catch (std::exception& e) {
    //    logfatal << "Path pool error:" << e.what() << corelib::logend;
    //    return false;
    //}
    try {
        if (!start_corelib(noModule)) {
            return false;
        }
    } catch (std::exception& e) {
        logfatal << "Start core LIB failure:" << e.what() << corelib::logend;
        return false;
    }
    run_ = true;
    logwcom("Engine") << "Engine started" << corelib::logend;
    return true;
}

bool EngineImpl::stop() {
    run_ = false;
    if (CommandOptionsRef.isDebug()) {
        stopAll();
    }
    return true;
}

bool EngineImpl::startModules() {
    if (!corelib::OS::isExists(CommandOptionsRef.getPluginPath())) {
        logecom("Engine") << "Module directory not found:" << CommandOptionsRef.getPluginPath() << logend;
        return true;
    }
    if (!moduleManager_.loadModule(CommandOptionsRef.getPluginPath())) {
        return false;
    }
    auto& modules = moduleManager_.getModules();
    for (auto& it : modules) {
        auto service = it.second.getService();
        ENGINE_PARAM_FAIL_RETURN_FALSE(!service);
        ENGINE_PARAM_FAIL_RETURN_FALSE(!service->install(this));
    }
    for (auto& it : modules) {
        auto service = it.second.getService();
        ENGINE_PARAM_FAIL_RETURN_FALSE(!service);
        ENGINE_PARAM_FAIL_RETURN_FALSE(!service->start(this));
    }
    return true;
}

void EngineImpl::processTimer() {
    auto timerID = timerQueue_.checkExpired();
    while (timerID != corelib::INVALID_TIMER_ID) {
        auto& modules = moduleManager_.getTypicalModules(ModuleType::EVENT_MODULE);
        for (auto& modulePtr : modules) {
            auto service = modulePtr->getService();
            try {
                service->onTimer(timerID);
            } catch (std::exception& e) {
                ENGINE_LOG_EXCEPTION(e, "TimerID:", timerID);
            }
        }
        timerID = timerQueue_.checkExpired();
    }
}

bool EngineImpl::startAsDaemon() {
    if (CommandOptionsRef.isDebug()) {
        return true;
    }
#ifdef WIN32
    return util::daemonize(nullptr, nullptr);
#else
    return util::daemonize(signalHandler, signalHandler);
#endif // WIN32
}

void EngineImpl::prefind() {
    for (auto it : prefindMap_) {
        findPath(it.first, 0);
        logvcom("Engine") << "Pre-find path for site type:" << it.first << logend;
    }
}

void EngineImpl::main_loop() {
    FrameDelayer frameDelayer(CommandOptionsRef.getFrame());
    while (run_) {
        now_ = TimeUtil::getMillionSecondsTimestampOS();
        frameDelayer.beginFrame(now_);
        grid_->update(now_);
        http_->runOnce();
        processTimer();
        componentManager_.update();
        try {
            moduleManager_.onTick(now_);
        } catch (std::exception& e) {
            ENGINE_LOG_EXCEPTION(e, "moduleManager_.onTick, timestamp:", now_);
        }
        frameDelayer.endFrame();
    }
    stopAll();
}

bool EngineImpl::start_corelib(bool noModule){
    // start timer queue
    if (!timerQueue_.start()) {
        logwcom("Engine") << "Start timer queue failed" << corelib::logend;
        return false;
    }
    // register callbacks
    if (!registerCallbacks()) {
        logwcom("Engine") << "Register GRID callback failed" << corelib::logend;
        return false;
    }
    // start grid
    if (!grid_->start()) {
        logwcom("Engine") << "Start GRID failed" << corelib::logend;
        return false;
    }
    // open http
    http_ = khttp::HttpFactory::createHttp();
    // load module
    if (!noModule) {
        if (!startModules()) {
            logwcom("Engine") << "Load plugin failed" << corelib::logend;
            return false;
        }
    }
    // pre-find path
    prefind();
    return true;
}

void EngineImpl::stopAll() {
    timerQueue_.stop();
    grid_->stop();
    if (http_) {
        http_->stop();
    }
    Actors.stop();
    moduleManager_.unloadAll();
    logwcom("Engine") << "Engine stopped" << corelib::logend;
    loggerRef.stop();
}

bool EngineImpl::internalFindPath(SiteType siteType, UserData userData) {
    return grid_->pathManager().find(siteType, userData, findTimeout_, findTimeout_);
}

bool EngineImpl::internalFindPath(const grid::SiteID & siteID, UserData userData) {
    return grid_->pathManager().find(siteID, userData, findTimeout_, findTimeout_);
}

void EngineImpl::removeEntityPath(PathID pathID) {
    for (auto it = locatorEntityMap_.begin(); it != locatorEntityMap_.end();) {
        if (it->second.pathID == pathID) {
            locatorEntityMap_.erase(it++);
        } else {
            it++;
        }
    }
}

ModuleManager & EngineImpl::getModuleManager() {
    return moduleManager_;
}

}}
